﻿using System.Reflection;
using Core;
using Core.Infrastructure;
using Core.Pipeline;
using Core.Pipeline.Attributes;
using Core.Plugins;
using MediatR;
using MediatR.Pipeline;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;

namespace Infrastructure.Extensions;

#nullable disable

/// <summary>
/// Represents application part manager extensions
/// </summary>
public static partial class ApplicationPartManagerExtensions
{
    #region Fields

    private static readonly IBoilerplateFileProvider _fileProvider;
    private static readonly List<KeyValuePair<string, Version>> _baseAppLibraries;
    private static readonly List<KeyValuePair<string, Version>> _pluginLibraries;
    private static readonly ReaderWriterLockSlim _locker = new();

    #endregion

    #region Ctor

    static ApplicationPartManagerExtensions()
    {
        //we use the default file provider, since the DI isn't initialized yet
        _fileProvider = CommonHelper.DefaultFileProvider;
        _baseAppLibraries = new List<KeyValuePair<string, Version>>();
        _pluginLibraries = new List<KeyValuePair<string, Version>>();

        //get all libraries from /bin/{version}/ directory
        foreach (var file in _fileProvider.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll"))
            _baseAppLibraries.Add(new KeyValuePair<string, Version>(file, GetAssemblyVersion(file)));

        //get all libraries from base site directory
        if (!AppDomain.CurrentDomain.BaseDirectory.Equals(Environment.CurrentDirectory, StringComparison.InvariantCultureIgnoreCase))
            foreach (var file in _fileProvider.GetFiles(Environment.CurrentDirectory, "*.dll"))
                _baseAppLibraries.Add(new KeyValuePair<string, Version>(file, GetAssemblyVersion(file)));

        //get all libraries from plugins directory
        DirectoryInfo pluginsDir = new DirectoryInfo(_fileProvider.Combine(Environment.CurrentDirectory, $"{BoilerplatePluginDefaults.PathName}"));
        foreach (DirectoryInfo subDir in pluginsDir.GetDirectories())
        {
            var pluginPathName = _fileProvider.Combine(subDir.FullName);
            if (_fileProvider.DirectoryExists(pluginPathName))
                foreach (var file in _fileProvider.GetFiles(pluginPathName, "*.dll"))
                    _pluginLibraries.Add(new KeyValuePair<string, Version>(file, GetAssemblyVersion(file)));
        }
    }

    #endregion

    #region Properties


    #endregion

    #region Utilities

    private static Version GetAssemblyVersion(string filePath)
    {
        try
        {
            return AssemblyName.GetAssemblyName(filePath).Version;
        }
        catch (BadImageFormatException)
        {
            //ignore
        }

        return null;
    }

    /// <summary>
    /// Load and register the assembly
    /// </summary>
    /// <param name="applicationPartManager">Application part manager</param>
    /// <param name="assemblyFile">Path to the assembly file</param>
    /// <param name="useUnsafeLoadAssembly">Indicating whether to load an assembly into the load-from context, bypassing some security checks</param>
    /// <returns>Assembly</returns>
    private static Assembly AddApplicationParts(ApplicationPartManager applicationPartManager, string assemblyFile, bool useUnsafeLoadAssembly)
    {
        //try to load a assembly
        Assembly assembly;

        try
        {
            assembly = Assembly.LoadFrom(assemblyFile);
        }
        catch (FileLoadException)
        {
            if (useUnsafeLoadAssembly)
            {
                //if an application has been copied from the web, it is flagged by Windows as being a web application,
                //even if it resides on the local computer.You can change that designation by changing the file properties,
                //or you can use the<loadFromRemoteSources> element to grant the assembly full trust.As an alternative,
                //you can use the UnsafeLoadFrom method to load a local assembly that the operating system has flagged as
                //having been loaded from the web.
                //see http://go.microsoft.com/fwlink/?LinkId=155569 for more information.
                assembly = Assembly.UnsafeLoadFrom(assemblyFile);
            }
            else
                throw;
        }

        //register the plugin definition
        applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly));

        return assembly;
    }

    /// <summary>
    /// Check whether the assembly is already loaded
    /// </summary>
    /// <param name="filePath">Assembly file path</param>
    /// <param name="pluginName">Plugin system name</param>
    /// <returns>Result of check</returns>
    private static bool IsAlreadyLoaded(string filePath, string pluginName)
    {
        //ignore already loaded libraries
        //(we do it because not all libraries are loaded immediately after application start)
        var fileName = _fileProvider.GetFileName(filePath);
        if (_baseAppLibraries.Any(library => library.Key.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)))
            return true;

        return true;
    }

    #endregion

    #region Methods

    /// <summary>
    /// 初始化插件
    /// </summary>
    /// <param name="applicationPartManager">Application part manager</param>
    /// <param name="services">services</param>
    public static void InitializePlugins(this ApplicationPartManager applicationPartManager, IServiceCollection services)
    {
        ArgumentNullException.ThrowIfNull(applicationPartManager);
        _pluginLibraries.AddRange(_baseAppLibraries);

        var assemblies = new List<Assembly>();
        // 1. 加载所有插件程序集
        var loadedAssemblyNames = new HashSet<string>();
        foreach (var plugin in _pluginLibraries)
        {
            var assembly = Assembly.LoadFrom(plugin.Key);

            // 获取程序集唯一标识（全名）
            var assemblyFullName = assembly.FullName;

            // 检查是否已加载过相同程序集
            if (loadedAssemblyNames.Contains(assemblyFullName))
            {
                continue; // 跳过重复程序集
            }

            // 记录已加载的程序集
            loadedAssemblyNames.Add(assemblyFullName);

            assemblies.Add(assembly);
            applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly));
        }

        // 2. 全局收集所有管道行为类型
        var allPipelineBehaviors = new List<Type>();

        // 3. 统一注册 MediatR（包含主程序集）
        assemblies.Add(Assembly.GetExecutingAssembly()); // 主程序集
        assemblies = assemblies.Where(x =>
            x.FullName.StartsWith("BP.Plugin") ||
            x.FullName.StartsWith("Core") ||
            x.FullName.StartsWith("Infrastructure")
        ).DistinctBy(x => x.FullName).ToList();

        // 4. 扫描所有程序集（含插件）
        foreach (var assembly in assemblies)
        {
            var behaviors = assembly.GetTypes()
                .Where(t =>
                    !t.IsAbstract &&
                    t.IsClass &&
                    t.GetInterfaces().Any(i =>
                        i.IsGenericType &&
                        i.GetGenericTypeDefinition() == typeof(IPipelineBehavior<,>)
                    ))
                .ToList();

            allPipelineBehaviors.AddRange(behaviors);
        }

        // 5. 按优先级排序（升序：数值小的先注册→后执行）
        var sortedBehaviors = allPipelineBehaviors
            .OrderBy(t => t.GetCustomAttribute<PipelineOrderAttribute>()?.Priority ?? int.MaxValue)
            .ToList();

        // 6. 注册MediatR（含行为管道）
        services.AddMediatR(config =>
        {
            config.RegisterServicesFromAssemblies(assemblies.ToArray());

            // 按顺序注册所有管道行为
            foreach (var behaviorType in sortedBehaviors)
            {
                config.AddOpenBehavior(behaviorType);
            }
        });
    }
    #endregion
}
